package com.ebs.md.api.abs;

/**
 * Perforce Revision:
 *  $File: //depot/MarketData/commons-md-api/MAIN/src/main/java/com/ebs/md/api/abs/BasicDetailedOrderBook.java $
 *  $Revision: #8 $  $Change: 114409 $  $DateTime: 2012/09/02 09:29:07 $  $Author: Arik.Sher $
 */

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.SortedMap;
import java.util.concurrent.locks.Lock;

import com.ebs.md.api.BookSide;
import com.ebs.md.api.IDetailedBookEntry;
import com.ebs.md.api.IIdentifiedOrder;
import com.ebs.md.api.IProduct;
import com.ebs.md.api.Rate;
import com.ebs.md.api.comparators.DetailedBookEntryComparator;
import com.ebs.md.api.orderbook.BookEntryPositionOfDeleted;


public class BasicDetailedOrderBook
extends AbstractOrderBook<IDetailedBookEntry, IIdentifiedOrder>
{
	protected final static Comparator<IDetailedBookEntry> bookEntryComparator =  new DetailedBookEntryComparator();

	// maps between order id(quote id) to the book entry
	protected Map<String, IDetailedBookEntry>  idsBidsMap;
	protected Map<String, IDetailedBookEntry>  idsOffersMap;
	

	public BasicDetailedOrderBook(IProduct proudct, boolean readyForUse) {
		super(proudct, readyForUse);
		if ( readyForUse ) {
			initTheOrdersMap(true);
		}
	}

	public BasicDetailedOrderBook(IProduct proudct) {
		this(proudct, false);
	}

	public BasicDetailedOrderBook(IProduct proudct,
			List<IDetailedBookEntry> bids, List<IDetailedBookEntry> offers) {
		super(proudct, bids, offers);

		initTheOrdersMap(true);
	}

	
	protected Comparator<IDetailedBookEntry> getBookEntryComparator() {
		return bookEntryComparator;
	}

	protected void postFullSnapshot() {
		initTheOrdersMap(true);
	}

	protected void initTheOrdersMap(boolean allocateNew) {
		if ( allocateNew ) {
			idsBidsMap   = new HashMap<String, IDetailedBookEntry>();
			idsOffersMap = new HashMap<String, IDetailedBookEntry>();
		}
		
		for ( NavigableSet<IDetailedBookEntry> currList : bids.values() ) {
			for ( IDetailedBookEntry currBE : currList ) {
				idsBidsMap.put(currBE.getQuoteId(), currBE);
			}
		}
		
		for ( NavigableSet<IDetailedBookEntry> currList : offers.values() ) {
			for ( IDetailedBookEntry currBE : currList ) {
				idsOffersMap.put(currBE.getQuoteId(), currBE);
			}
		}
		
	}
	
	protected Map<String, IDetailedBookEntry> getOrdersMap(BookSide bookSide) {
		if ( BookSide.BID.equals(bookSide) ) {
			return idsBidsMap;
		}
		if ( BookSide.OFFER.equals(bookSide) ) {
			return idsOffersMap;
		}
		// not likely to get here...
		throw new RuntimeException("book side not supported " + bookSide);
	}
	
	protected void handleAddition(IIdentifiedOrder order) {
		BookSide side = BookSide.fromOrderSide(order.getSide());
		PrefixedLogger logger  = getLogger(side);
		
		Map<String, IDetailedBookEntry> ordersMap = getOrdersMap(side);
		
		String orderId = order.getId();
		
		if ( ordersMap.containsKey(orderId) ) {
			logger.warn(String.format("order id '%s' already exists in book... thus cannot add it (again).", orderId) );
		} else {
			BasicDetailedBookEntry bdbe = new BasicDetailedBookEntry(order);
		
			SortedMap<Rate, NavigableSet<IDetailedBookEntry>> levelBook = getAffectedOBSide(order.getSide());
			
			NavigableSet<IDetailedBookEntry> entriesAtRate = levelBook.get(bdbe.getRate());
			
			if (orderBookListener != null )
				orderBookListener.beforeNewBookEntry(this, bdbe, order);
			
			Lock lockObj  = getLockObject(side);
			// atomic action - start - acquire lock
			lockObj.lock();
			
			ordersMap.put(orderId, bdbe);
			if ( entriesAtRate != null ) {
				entriesAtRate.add(bdbe);
			} else {
				levelBook.put(bdbe.getRate(), createBookEntriesAtRate(bdbe));
			}
			// atomic action - END - release lock
			lockObj.unlock();

			if (orderBookListener != null )
				orderBookListener. afterNewBookEntry(this, bdbe, order, snapshotEntryPosition(bdbe));
		}
		
	}

	protected void handleRemoval(IIdentifiedOrder order) {
		BookSide side = BookSide.fromOrderSide(order.getSide());
		PrefixedLogger logger  = getLogger(side);
		
		Map<String, IDetailedBookEntry> ordersMap = getOrdersMap(side);
		
		String orderId = order.getId();
		IDetailedBookEntry bookEntry = ordersMap.get(orderId);
		
		if ( bookEntry == null ) {
			logger.warn(String.format("order id '%s' does not exists in book... thus cannot remove it.", orderId) );
		} else {
			SortedMap<Rate, NavigableSet<IDetailedBookEntry>> levelBook = getAffectedOBSide(order.getSide());
			NavigableSet<IDetailedBookEntry> entriesAtRate = levelBook.get(bookEntry.getRate());
			
			BookEntryPositionOfDeleted entryPositionBeforeRemoval = snapshotEntryPositionOfDeleted(bookEntry);
			
			if (orderBookListener != null )
				orderBookListener.beforeRemoveBookEntry(this, bookEntry, order);

			Lock lockObj  = getLockObject(side);
			// atomic action - start - acquire lock
			lockObj.lock();
			ordersMap.remove(orderId);
			
			if ( entriesAtRate == null ) {
				logger.warn(String.format("entries at rate '%s', do not contaion BookEntryId '%s'", bookEntry.getRate(), bookEntry.getQuoteId()));
			} else {
				entriesAtRate.remove(bookEntry);
				if ( entriesAtRate.isEmpty() ) {
					levelBook.remove(bookEntry.getRate());
				}
			}
			// atomic action - END - release lock
			lockObj.unlock();
			
			if (orderBookListener != null )
				orderBookListener. afterRemoveBookEntry(this, bookEntry, order, entryPositionBeforeRemoval);
		}
		
	}

	protected void handleAmountIncrease(IIdentifiedOrder order) {
		BookSide side = BookSide.fromOrderSide(order.getSide());
		PrefixedLogger logger  = getLogger(side);
		
		Map<String, IDetailedBookEntry> ordersMap = getOrdersMap(side);
		
		String orderId = order.getId();
		IDetailedBookEntry bookEntry = ordersMap.get(orderId);
		
		if ( bookEntry == null ) {
			logger.warn(String.format("order id '%s' does not exists in book... thus cannot increase its amount.", orderId) );
		} else {
			if (orderBookListener != null ) {
				orderBookListener.beforeModifyBookEntry(this, bookEntry, order);
			}

			Lock lockObj  = getLockObject(side);
			// atomic action - start - acquire lock
			lockObj.lock();
			bookEntry.increaseAmount(order.getAmount());
			// atomic action - END - release lock
			lockObj.unlock();

			if (orderBookListener != null ) {
				orderBookListener.beforeModifyBookEntry(this, bookEntry, order);
			}
		}
		
	}

	protected void handleAmountDecrease(IIdentifiedOrder order) {
		BookSide side = BookSide.fromOrderSide(order.getSide());
		PrefixedLogger logger  = getLogger(side);
		
		Map<String, IDetailedBookEntry> ordersMap = getOrdersMap(side);
		
		String orderId = order.getId();
		IDetailedBookEntry bookEntry = ordersMap.get(orderId);
		
		if ( bookEntry == null ) {
			logger.warn(String.format("order id '%s' does not exists in book... thus cannot decrease its amount.", orderId) );
		} else {
			if (orderBookListener != null ) {
				orderBookListener.beforeModifyBookEntry(this, bookEntry, order);
			}

			Lock lockObj  = getLockObject(side);
			// atomic action - start - acquire lock
			lockObj.lock();
			bookEntry.decreaseAmount(order.getAmount());
			// atomic action - END - release lock
			lockObj.unlock();

			if (orderBookListener != null ) {
				orderBookListener. afterModifyBookEntry(this, bookEntry, order);
			}
		}
		
	}

}
